// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Analyzer.Utilities.PooledObjects { [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not used in this context")] [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "The 'Dictionary' suffix is intentional")] internal struct TemporaryDictionary<TKey, TValue> where TKey : notnull { #pragma warning disable CS0649 // Field 'TemporaryDictionary<TKey, TValue>.Empty' is never assigned to, and will always have its default value public static readonly TemporaryDictionary<TKey, TValue> Empty; #pragma warning restore CS0649 // Field 'TemporaryDictionary<TKey, TValue>.Empty' is never assigned to, and will always have its default value /// <summary> /// An empty dictionary used for creating non-null enumerators when no items have been added to the dictionary. /// </summary> private static readonly Dictionary<TKey, TValue> EmptyDictionary = new(); // 🐇 PERF: use PooledDictionary<TKey, TValue> instead of PooledConcurrentDictionary<TKey, TValue> due to // allocation overhead in clearing the set for returning it to the pool. private PooledDictionary<TKey, TValue>? _storage; public readonly Enumerable NonConcurrentEnumerable => new(_storage ?? EmptyDictionary); public void Free(CancellationToken cancellationToken) { Interlocked.Exchange(ref _storage, null)?.Free(cancellationToken); } private PooledDictionary<TKey, TValue> GetOrCreateStorage(CancellationToken cancellationToken) { if (_storage is not { } storage) { var newStorage = PooledDictionary<TKey, TValue>.GetInstance(); storage = Interlocked.CompareExchange(ref _storage, newStorage, null) ?? newStorage; if (storage != newStorage) { // Another thread initialized the value. Make sure to release the unused object. newStorage.Free(cancellationToken); } } return storage; } internal void Add(TKey key, TValue value, CancellationToken cancellationToken) { var storage = GetOrCreateStorage(cancellationToken); lock (storage) { storage.Add(key, value); } } public readonly struct Enumerable { private readonly Dictionary<TKey, TValue> _dictionary; public Enumerable(Dictionary<TKey, TValue> dictionary) { _dictionary = dictionary; } public Enumerator GetEnumerator() => new(_dictionary.GetEnumerator()); } public struct Enumerator { private Dictionary<TKey, TValue>.Enumerator _enumerator; public Enumerator(Dictionary<TKey, TValue>.Enumerator enumerator) { _enumerator = enumerator; } public bool MoveNext() => _enumerator.MoveNext(); public KeyValuePair<TKey, TValue> Current => _enumerator.Current; } } } |